Skip to content

feat(groups): repair a member's lost share - #42

Merged
USCMig merged 1 commit into
mainfrom
feat/share-repair
Oct 26, 2026
Merged

USCMig merged 1 commit into
mainfrom
feat/share-repair

Conversation

@USCMig

@USCMig USCMig commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #41 (accounting), and through it on #34–#40. Merge those first; until then this diff also shows their changes.

Summary

Share repair. A member who lost their device and has no backup gets their share back from as many members as the threshold, without moving the funds.

  • The scheme: FROST's repairable threshold scheme, frost_core::keys::repairable.
  • Privacy: the helpers never learn the share.
  • Limit: a group where every member must sign (2 of 2, 3 of 3) can't repair a share.

The flow

  1. On the new device: open Groups → Recover a group. Send a member your contact code and read them its safety code.
  2. A helper: open the group's page → Recovery & share repair → Repair a member's share. Choose the member, paste their new contact code, tick that its safety code matches, choose the helpers, and click Start repair. Send the member the repair code it shows.
  3. The other helpers: the request appears under Approvals with the same safety code. Click Approve and help.
  4. On the new device: paste the repair code and click Recover. The group appears with its wallet syncing from where it started.

Protocol

It runs over a frostd session marked REPAIR_SESSION that holds the helpers and the member's new key.

Message From To Carries
Request the helper who started everyone the member, their new key, the helpers
Delta each helper each other helper step 1
Share each helper the member step 2, plus the group's data: public key package, members, viewing seed, and where its wallet starts scanning
Done the member the helpers whether the rebuilt share checked out

Checks

  • A joining helper refuses a session that isn't exactly the named helpers plus one key from outside the group, or whose member is a helper or isn't a member.
  • The member's app keeps nothing unless:
    • every helper describes the same group;
    • that group names this device's key as the member's;
    • the rebuilt key package matches the group's public verifying share for that member.
    • Wallet start heights may differ between helpers, since each app records its own; the earliest is kept.

The member's new key

  • This is the trust decision. The member comes back with a new communication key, so helpers compare its safety code with them before approving.
  • Helpers switch first. Each helper's app vouches for the new key in the group's records (MemberKey), then switches. The vouch goes out to the members as they were before the switch: an app that doesn't know the new key yet would refuse a session that includes it.
  • The other members switch once enough helpers vouch. That means as many as a payment needs (polls::vouched_member_keys). One member alone can't redirect another's identity.
  • The starting helper keeps the session open a minute after success, so the other helpers can still read the member's confirmation.

Stolen, not lost

Repair rebuilds the same share. If a device may have been stolen, whoever has it still holds the share. The UI and the user guide say to move the funds to a new group instead. Share refresh (new shares for the same key) is listed in TODO.md as a follow-up.

Changes

  • Core
    • New repair.rs: the protocol, RepairCode (with chat-safe decoding), and the pure deltas and rebuild functions.
    • polls.rs: MemberKey and vouched_member_keys.
    • dkg.rs: SessionIo helpers made crate-visible.
  • App
    • New commands/repair.rs: start_repair, join_repair (approved with respond_to_signing), read_repair_code, receive_repair, apply_vouched_keys.
    • contacts.rs: read_contact.
    • Pending sessions gain the kind repair, matched to a group with exactly one outside key.
    • Vouched keys are applied as group records arrive.
  • Frontend
    • Recover a group on Groups.
    • Repair a member's share, replacing the guide that said repair wasn't built yet.
    • Repair requests in Approvals, counted in the badge.
    • Repair events in the ceremony store.
  • Docs: user guide (Repairing a lost share), CHANGELOG, PRIVACY.md, TODO.md.

Testing

  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • tsc, then vitest (55 tests)
  • Core tests:
    • helpers_rebuild_a_lost_share_that_matches_the_groups_record_of_it: RedPallas and Ed25519, with an exact key package and a wrong part refused.
    • Group data must agree and name the new key, with differing start heights tolerated.
    • The repair code survives being pasted through a chat app.
    • a_members_new_key_counts_once_enough_members_vouch_for_it.
  • Live test over a real frostd, tests/repair.rs (passed 3 of 3 runs):
    • Carol's share in a 2-of-4 group is repaired onto a new device, with Alice and Bob helping.
    • The device ends up with Carol's key package byte for byte, and the group's viewing seed.
    • Alice, Bob and Erin (who didn't help) all switch to the new key.
  • Frontend tests:
    • Starting a repair, gated on the safety-code comparison and enough helpers.
    • Recovering on a new device.
    • Reviewing and approving a repair request in Approvals.

🤖 Generated with Claude Code

A member who lost their device and backup gets their share back from as
many members as the threshold, with FROST's repairable threshold scheme
(frost_core::keys::repairable), without moving the funds. The helpers
never learn the share, and a group where every member must sign can't
repair one.

core/src/repair.rs runs it over a session marked REPAIR_SESSION, holding
the helpers and the member's new communication key: the helper who
starts sends a Request; each helper sends every other a Delta (step 1),
sums what it got and sends the member a Share (step 2) with the group's
data (its public key package, members, viewing seed and where its wallet
starts); the member's app rebuilds the key package (step 3), checks it
against the group's public verifying share for them, and answers Done.
A joining helper refuses a session that isn't exactly the named helpers
plus one outside key, or whose member is a helper or isn't a member.
The member's app keeps nothing unless every helper describes the same
group naming its key (start heights aside: the earliest is kept).

The member comes back with a new key, so helpers compare its safety code
with them before approving, the trust decision. Each helper's app
vouches for the new key in the group's records (MemberKey), delivered to
the members as they were, then switches; the other members' apps switch
once as many members vouch as a payment needs. The starting helper keeps
the session open a minute after success, so the others can still read
the member's confirmation.

Repair rebuilds the same share, so the interface says to move the funds
instead if a device may have been stolen.

Interface: Recover a group on the new device (its contact code, then the
repair code), Repair a member's share on the group's page, requests with
the new key's safety code in Approvals.

Tested: unit tests rebuild a dealer-made share exactly (RedPallas and
Ed25519) and refuse a wrong part; a live test over frostd repairs Carol's
share in a 2-of-4 group onto a new device, byte for byte with its viewing
seed, and Erin's app, which didn't help, switches to the new key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@USCMig
USCMig merged commit 196b521 into main Oct 26, 2026
1 check passed
@USCMig
USCMig deleted the feat/share-repair branch October 26, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant